package com.smartleanx.module.hrm.service.payroll;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.smartleanx.framework.common.enums.BizTypeEnum;
import com.smartleanx.framework.common.pojo.PageResult;
import com.smartleanx.framework.common.util.collection.CollectionUtils;
import com.smartleanx.framework.common.util.object.BeanUtils;
import com.smartleanx.framework.common.util.validation.ValidationUtils;
import com.smartleanx.framework.customfield.core.service.CustomFieldDataService;
import com.smartleanx.framework.customfield.core.service.bo.CustomFieldDataListReqBO;
import com.smartleanx.framework.customfield.core.service.bo.CustomFieldDataRespBO;
import com.smartleanx.framework.customfield.core.service.bo.CustomFieldDataSaveBO;
import com.smartleanx.framework.customfield.core.service.bo.inner.CustomFieldDataBaseBO;
import com.smartleanx.framework.datapermission.core.util.DataPermissionUtils;
import com.smartleanx.module.bpm.enums.task.BpmProcessInstanceStatusEnum;
import com.smartleanx.module.hrm.controller.admin.payroll.vo.PayrollDetailCommonReqVO;
import com.smartleanx.module.hrm.controller.admin.payroll.vo.PayrollDetailPageReqVO;
import com.smartleanx.module.hrm.controller.admin.payroll.vo.PayrollDetailSaveReqVO;
import com.smartleanx.module.hrm.convert.payroll.PayrollDetailConvert;
import com.smartleanx.module.hrm.dal.dataobject.employee.EmployeeDO;
import com.smartleanx.module.hrm.dal.dataobject.payroll.PayrollDO;
import com.smartleanx.module.hrm.dal.dataobject.payroll.PayrollDetailDO;
import com.smartleanx.module.hrm.dal.mysql.payroll.PayrollDetailMapper;
import com.smartleanx.module.hrm.eums.employee.EmployeeStatusEnum;
import com.smartleanx.module.hrm.eums.payroll.PayrollSendStatusEnum;
import com.smartleanx.module.hrm.intergration.AdminUserServiceProvider;
import com.smartleanx.module.hrm.intergration.DeptServiceProvider;
import com.smartleanx.module.hrm.intergration.PostLevelServiceProvider;
import com.smartleanx.module.hrm.intergration.PostServiceProvider;
import com.smartleanx.module.hrm.service.employee.EmployeeService;
import com.smartleanx.module.hrm.service.payroll.bo.PayrollDetailFieldSaveBO;
import com.smartleanx.module.hrm.service.payroll.bo.PayrollDetailSaveBO;
import com.smartleanx.module.hrm.service.payrollapproverecord.PayrollApproveRecordService;
import com.smartleanx.module.hrm.service.payrollapproverecord.bo.PayrollApproveRecordDetailSaveBO;
import com.smartleanx.module.hrm.service.salarynotice.SalaryNoticeService;
import com.smartleanx.module.hrm.service.salarynotice.bo.SalaryNoticeSaveBO;
import com.smartleanx.module.system.api.dept.dto.DeptRespDTO;
import com.smartleanx.module.system.api.dept.dto.PostLevelRespDTO;
import com.smartleanx.module.system.api.dept.dto.PostRespDTO;
import com.smartleanx.module.system.api.user.dto.AdminUserRespDTO;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.util.*;

import static com.smartleanx.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
import static com.smartleanx.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.smartleanx.module.hrm.enums.ErrorCodeConstants.*;

/**
 * 薪资详情 Service 实现类
 *
 * @author zhangchp
 */
@Service
@Validated
public class PayrollDetailServiceImpl implements PayrollDetailService {

    @Resource
    private PayrollDetailMapper payrollDetailMapper;

    @Resource
    private EmployeeService employeeService;

    @Resource
    private CustomFieldDataService customFieldDataService;

    @Resource
    private AdminUserServiceProvider adminUserServiceProvider;

    @Resource
    private PayrollApproveRecordService payrollApproveRecordService;

    @Resource
    private SalaryNoticeService salaryNoticeService;

    @Resource
    private DeptServiceProvider deptServiceProvider;

    @Resource
    private PostServiceProvider postServiceProvider;

    @Resource
    private PostLevelServiceProvider postLevelServiceProvider;

    @Lazy
    @Resource
    private PayrollService payrollService;

    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.NESTED) // 添加事务，异常则回滚所有导入
    public PayrollDetailDO createDetail(Collection<PayrollDetailFieldSaveBO> createBOs, Long payrollId) {
        // 准备
        PayrollDetailSaveBO saveBO = parsePayrollDetail(createBOs, payrollId);
        fillEmployeeInfo(Collections.singleton(saveBO));
        // 校验
        validatePayrollIdAndEmployeeIdUncreated(payrollId, Long.valueOf(saveBO.getEmployeeId().toString()), createBOs);
        validatePayrollIdAndEmployeeIdNotExists(payrollId, Long.valueOf(saveBO.getEmployeeId().toString()), saveBO.getEmployeeNo());
        // 插入
        AdminUserRespDTO loginUser = adminUserServiceProvider.getLoginUser();
        if(Objects.isNull(loginUser.getDeptId())){
            throw exception(PAYROLL_DETAIL_USER_DEPT_ID_NULL);
        }
        PayrollDetailDO detail = PayrollDetailConvert.INSTANCE.convertDO(saveBO);
        detail.setUserId(loginUser.getId());
        detail.setDeptId(loginUser.getDeptId());
        payrollDetailMapper.insert(detail);
        // 返回
        return detail;
    }


    @Override
    @Transactional(rollbackFor = Exception.class) // 添加事务，异常则回滚所有导入
    public void updateDetail(PayrollDetailSaveReqVO updateReqVO) {
        // 校验
        List<PayrollDetailSaveBO> saveBOs = CollectionUtils.convertList(updateReqVO.getDatas(), reqVOs ->
                parsePayrollDetail(BeanUtils.toBean(reqVOs, PayrollDetailFieldSaveBO.class), updateReqVO.getPayrollId()));
        validateEmployeeNoDuplicate(saveBOs);
        fillEmployeeInfo(saveBOs);
        // 校验
        validateEmployeeIdExists(CollectionUtils.convertList(saveBOs, saveBO -> Long.valueOf(saveBO.getEmployeeId().toString())), updateReqVO.getPayrollId());
        validateApproveResultAndSendStatusByEmployeeIdAndPayrollId(updateReqVO.getPayrollId(),
                CollectionUtils.convertList(saveBOs, saveBO -> Long.valueOf(saveBO.getEmployeeId().toString())));
        validateAllowUpdate(CollectionUtils.convertList(saveBOs, saveBO -> Long.valueOf(saveBO.getEmployeeId().toString())), updateReqVO.getPayrollId());
        // 更新
        payrollDetailMapper.updateColumnBatchByPayrollIdAndEmployeeId(CollectionUtils.convertList(saveBOs, PayrollDetailConvert.INSTANCE::convertDO));
        List<CustomFieldDataBaseBO> fieldList = BeanUtils.toBean(CollectionUtils.newArrayList(updateReqVO.getDatas()), CustomFieldDataBaseBO.class);
        customFieldDataService.crudData(CustomFieldDataSaveBO.builder().datas(fieldList).bizType(BizTypeEnum.PAYROLL.getValue())
                .labels(CollectionUtils.convertSet(fieldList, CustomFieldDataBaseBO::getLabel))
                .labelGroups(CollectionUtils.convertSet(fieldList, CustomFieldDataBaseBO::getLabelGroup))
                .recordIds(CollectionUtils.convertList(fieldList, CustomFieldDataBaseBO::getRecordId)).build());
    }

    @Override
    @Transactional(rollbackFor = Exception.class) // 添加事务，异常则回滚所有导入
    public void approve(PayrollDetailCommonReqVO reqVO) {
        // 校验
        if(CollUtil.isEmpty(reqVO.getIds())){
            throw exception(PAYROLL_DETAIL_APPROVE_EMPTY);
        }
        validateDetailExists(reqVO.getIds());
        validateIdAndPayrollIdConsistent(reqVO.getIds(), reqVO.getPayrollId());
        validateApproveResultAndSendStatus(reqVO.getIds());
        validateUpdated(reqVO.getIds());
        // 处理
        List<PayrollDetailDO> detailList = payrollDetailMapper.selectBatchIds(reqVO.getIds());
        List<CustomFieldDataRespBO> fieldDataList = customFieldDataService.getDataList(
                CustomFieldDataListReqBO.builder().bizType(BizTypeEnum.PAYROLL.getValue())
                        .recordIds(CollectionUtils.convertList(detailList, PayrollDetailDO::getId)).build());
        Map<Long, Map<String, Object>> detailMap = CollectionUtils.convertMap(detailList, PayrollDetailDO::getId, BeanUtil::beanToMap);
        fieldDataList.forEach(fieldData -> {
            Object fieldValue = detailMap.get(fieldData.getRecordId()).get(StringUtils.underlineToCamel(fieldData.getColName()));
            if(Objects.nonNull(fieldValue)){
                fieldData.setFieldValue(Objects.toString(fieldValue));
            }
        });
        payrollApproveRecordService.createRecord(CollectionUtils.convertList(detailList, detail -> {
            PayrollApproveRecordDetailSaveBO saveBO = BeanUtils.toBean(detail, PayrollApproveRecordDetailSaveBO.class);
            saveBO.setPayrollDetailId(detail.getId());
            return saveBO;
        }), BeanUtils.toBean(fieldDataList, CustomFieldDataBaseBO.class), reqVO.getPayrollId());
        payrollDetailMapper.updateApproveResult(BpmProcessInstanceStatusEnum.RUNNING.getStatus(), CollectionUtils.convertList(detailList, PayrollDetailDO::getId));
    }

    @Override
    public void updateApproveResult(Collection<Long> ids, Integer approveResult) {
        // 校验
        validateDetailExists(ids);
        // 更新
        payrollDetailMapper.updateApproveResultAndUpdated(approveResult, !BpmProcessInstanceStatusEnum.APPROVE.equals(approveResult), ids);
    }

    @Override
    public void generatePayStub(PayrollDetailCommonReqVO reqVO) {
        // 校验
        if(CollUtil.isEmpty(reqVO.getIds())){
            throw exception(PAYROLL_DETAIL_PAY_STUB_EMPTY);
        }
        validateDetailExists(reqVO.getIds());
        validateIdAndPayrollIdConsistent(reqVO.getIds(), reqVO.getPayrollId());
        validateApprovePassedAndNotSend(reqVO.getPayrollId(), reqVO.getIds());
        // 更新
        payrollDetailMapper.updateSendStatusAndReadStatusAndConfirmStatusAndFeedbackByPayrollIdAndId(
                PayrollSendStatusEnum.UNSENT.getValue(), null, null, "", reqVO.getPayrollId(), reqVO.getIds());
    }

    @Override
    @Transactional(rollbackFor = Exception.class) // 添加事务，异常则回滚所有导入
    public void sendPayStub(PayrollDetailCommonReqVO reqVO) {
        // 校验
        if(CollUtil.isEmpty(reqVO.getIds())){
            throw exception(PAYROLL_DETAIL_PAY_STUB_EMPTY);
        }
        validateDetailExists(reqVO.getIds());
        validateIdAndPayrollIdConsistent(reqVO.getIds(), reqVO.getPayrollId());
        validateApprovePassedAndNotSend(reqVO.getPayrollId(), reqVO.getIds());
        // 处理
        PayrollDO payroll = payrollService.getPayroll(reqVO.getPayrollId());
        List<PayrollDetailDO> detailList = payrollDetailMapper.selectBatchIds(reqVO.getIds());
        List<CustomFieldDataRespBO> fieldDataList = customFieldDataService.getDataList(
                CustomFieldDataListReqBO.builder().bizType(BizTypeEnum.PAYROLL.getValue())
                        .recordIds(CollectionUtils.convertList(detailList, PayrollDetailDO::getId)).build());
        Map<Long, Map<String, Object>> detailMap = CollectionUtils.convertMap(detailList, PayrollDetailDO::getId, BeanUtil::beanToMap);
        fieldDataList.forEach(fieldData -> {
            Object fieldValue = detailMap.get(fieldData.getRecordId()).get(StringUtils.underlineToCamel(fieldData.getColName()));
            if(Objects.nonNull(fieldValue)){
                fieldData.setFieldValue(Objects.toString(fieldValue));
            }
        });
        payrollDetailMapper.updateSendStatusAndReadStatusAndConfirmStatusAndFeedbackByPayrollIdAndId(
                PayrollSendStatusEnum.SENT.getValue(), false, null, "", reqVO.getPayrollId(), reqVO.getIds());
        // 筛选
        Set<Long> employeeIdSet = CollectionUtils.convertSet(employeeService.getEmployeeList(
                CollectionUtils.convertList(detailList, PayrollDetailDO::getEmployeeId)), EmployeeDO::getId,
                employee -> !EmployeeStatusEnum.SEPARATED.getCode().equals(employee.getState()));
        List<PayrollDetailDO> needSendList = CollectionUtils.filterList(detailList, detail -> employeeIdSet.contains(detail.getEmployeeId()));
        if(CollUtil.isNotEmpty(needSendList)){
            // 发送
            Set<Long> detailIdSet = CollectionUtils.convertSet(needSendList, PayrollDetailDO::getId);
            salaryNoticeService.createNotice(CollectionUtils.convertList(needSendList, detail -> {
                SalaryNoticeSaveBO saveBO = BeanUtils.toBean(detail, SalaryNoticeSaveBO.class);
                saveBO.setTitle(payroll.getYear() + "年" + payroll.getMonth() + "月工资条");
                saveBO.setPayrollDetailId(detail.getId());
                return saveBO;
            }), CollectionUtils.filterList(BeanUtils.toBean(fieldDataList, CustomFieldDataBaseBO.class),
                    fieldData -> detailIdSet.contains(fieldData.getRecordId())));
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class) // 添加事务，异常则回滚所有导入
    public void withdrawPayStub(PayrollDetailCommonReqVO reqVO) {
        // 校验
        if(CollUtil.isEmpty(reqVO.getIds())){
            throw exception(BAD_REQUEST);
        }
        validateDetailExists(reqVO.getIds());
        validateIdAndPayrollIdConsistent(reqVO.getIds(), reqVO.getPayrollId());
        validateAllowWithdraw(reqVO.getIds());
        // 处理
        List<PayrollDetailDO> detailList = payrollDetailMapper.selectBatchIds(reqVO.getIds());
        payrollDetailMapper.updateSendStatus(PayrollSendStatusEnum.WITHDRAW.getValue(), reqVO.getIds());
        // 筛选
        Set<Long> employeeIdSet = CollectionUtils.convertSet(employeeService.getEmployeeList(
                        CollectionUtils.convertList(detailList, PayrollDetailDO::getEmployeeId)), EmployeeDO::getId,
                employee -> !EmployeeStatusEnum.SEPARATED.getCode().equals(employee.getState()));
        CollectionUtils.filterList(detailList, detail -> employeeIdSet.contains(detail.getEmployeeId()))
                .forEach(detail -> salaryNoticeService.deleteNoticeByPayrollDetailId(detail.getId()));
    }

    @Override
    public void updateReadStatus(Long id) {
        // 校验
        validateDetailExists(id);
        // 更新
        payrollDetailMapper.updateById(PayrollDetailDO.builder().id(id).readStatus(true).build());
    }

    @Override
    public void updateConfirmStatus(Long id, Boolean confirmStatus, String feedback) {
        // 校验
        validateDetailExists(id);
        validatePayrollNotArchive(id);
        validateDetailNotArchive(id);
        // 更新
        payrollDetailMapper.updateById(PayrollDetailDO.builder().id(id).confirmStatus(confirmStatus).feedback(feedback)
                .updated(Boolean.FALSE.equals(confirmStatus)).build());
    }

    @Override
    public void confirmPayStub(PayrollDetailCommonReqVO reqVO) {
        // 校验
        if(CollUtil.isEmpty(reqVO.getIds())){
            throw exception(BAD_REQUEST);
        }
        validateDetailExists(reqVO.getIds());
        validateIdAndPayrollIdConsistent(reqVO.getIds(), reqVO.getPayrollId());
        validateEmployeeRetire(reqVO.getIds());
        validatePayrollNotArchiveByPayrollId(reqVO.getPayrollId());
        validateDetailNotArchive(reqVO.getIds());
        // 更新
        payrollDetailMapper.updateConfirmStatus(true, reqVO.getIds());
    }

    @Override
    public void cancelPayStub(PayrollDetailCommonReqVO reqVO) {
        // 校验
        if(CollUtil.isEmpty(reqVO.getIds())){
            throw exception(BAD_REQUEST);
        }
        validateDetailExists(reqVO.getIds());
        validateIdAndPayrollIdConsistent(reqVO.getIds(), reqVO.getPayrollId());
        validateEmployeeRetire(reqVO.getIds());
        validatePayrollNotArchiveByPayrollId(reqVO.getPayrollId());
        validateDetailNotArchive(reqVO.getIds());
        // 更新
        payrollDetailMapper.updateConfirmStatus(false, reqVO.getIds());
    }

    @Override
    public void archive(Long payrollId) {
        // 校验
        validateArchiveDetailExists(payrollId);
        validateAllConfirm(payrollId);
        // 更新
        payrollDetailMapper.updateArchivedByPayrollId(true, payrollId);
    }

    private PayrollDetailSaveBO parsePayrollDetail(Collection<PayrollDetailFieldSaveBO> saveBOs, Long payrollId) {
        Map<String, Object> map = new HashMap<>();
        saveBOs.forEach(createBO -> map.put(StringUtils.underlineToCamel(createBO.getColName()), createBO.getFieldValue()));
        PayrollDetailSaveBO saveBO = BeanUtil.toBean(map, PayrollDetailSaveBO.class);
        saveBO.setPayrollId(payrollId);
        ValidationUtils.validate(saveBO);
        return saveBO;
    }

    private void fillEmployeeInfo(Collection<PayrollDetailSaveBO> saveBOs) {
        List<String> employeeNos = CollectionUtils.convertList(saveBOs, PayrollDetailSaveBO::getEmployeeNo);
        if(CollUtil.isEmpty(employeeNos)){
            throw exception(PAYROLL_DETAIL_EMPLOYEE_NO_NULL);
        }
        List<EmployeeDO> employeeList = new ArrayList<>();
        DataPermissionUtils.executeIgnore(() -> employeeList.addAll(employeeService.selectListByEmployeeNoS(employeeNos)));
        Map<Long, String> deptMap = CollectionUtils.convertMap(deptServiceProvider.getDeptList(
                CollectionUtils.convertSet(employeeList, EmployeeDO::getDeptId)), DeptRespDTO::getId, DeptRespDTO::getName),
                postMap = CollectionUtils.convertMap(postServiceProvider.getPostList(
                        CollectionUtils.convertSet(employeeList, EmployeeDO::getPostId)), PostRespDTO::getId, PostRespDTO::getName),
                postLevelMap = CollectionUtils.convertMap(postLevelServiceProvider.getPostLevelList(
                        CollectionUtils.convertSet(employeeList, EmployeeDO::getPostLevelId)), PostLevelRespDTO::getId, PostLevelRespDTO::getName);
        Map<String, EmployeeDO> employeeMap = CollectionUtils.convertMap(employeeList, EmployeeDO::getEmployeeNo);
        saveBOs.forEach(saveBO -> {
            if(StrUtil.isBlank(saveBO.getEmployeeNo())){
                throw exception(PAYROLL_DETAIL_EMPLOYEE_NO_NULL);
            }
            if(!employeeMap.containsKey(saveBO.getEmployeeNo())){
                throw exception(PAYROLL_DETAIL_EMPLOYEE_NO_NOT_EXISTS, saveBO.getEmployeeNo());
            }
            EmployeeDO employee = employeeMap.get(saveBO.getEmployeeNo());
            if(!employee.getName().equals(saveBO.getEmployeeId().toString())){
                throw exception(PAYROLL_DETAIL_EMPLOYEE_NAME_ERROR, saveBO.getEmployeeNo());
            }
            saveBO.setEmployeeId(employee.getId());
            if(!((Objects.isNull(employee.getDeptId()) && StrUtil.isBlank(saveBO.getEmployeeDeptId())) ||
                    Objects.equals(deptMap.get(employee.getDeptId()), saveBO.getEmployeeDeptId()))){
                throw exception(PAYROLL_DETAIL_EMPLOYEE_DEPT_ERROR, saveBO.getEmployeeNo());
            }
            if(!((Objects.isNull(employee.getPostId()) && StrUtil.isBlank(saveBO.getEmployeePostId())) ||
                    Objects.equals(postMap.get(employee.getPostId()), saveBO.getEmployeePostId()))){
                throw exception(PAYROLL_DETAIL_EMPLOYEE_POST_ERROR, saveBO.getEmployeeNo());
            }
            if(!((Objects.isNull(employee.getPostLevelId()) && StrUtil.isBlank(saveBO.getEmployeePostLevelId())) ||
                    Objects.equals(postLevelMap.get(employee.getPostLevelId()), saveBO.getEmployeePostLevelId()))){
                throw exception(PAYROLL_DETAIL_EMPLOYEE_POST_LEVEL_ERROR, saveBO.getEmployeeNo());
            }
        });
    }


    @Override
    @Transactional(rollbackFor = Exception.class) // 添加事务，异常则回滚所有导入
    public void deleteDetail(Long id) {
        // 校验
        validateDetailExists(id);
        validateAllowDelete(id);
        // 删除
        payrollDetailMapper.deleteById(id);
        customFieldDataService.crudData(CustomFieldDataSaveBO.builder().bizType(BizTypeEnum.PAYROLL.getValue())
                .labels(Collections.emptyList()).labelGroups(Collections.emptyList()).recordIds(Collections.singletonList(id)).build());
    }


    private void validateDetailExists(Long id) {
        if (payrollDetailMapper.selectById(id) == null) {
            throw exception(PAYROLL_DETAIL_NOT_EXISTS);
        }
    }

    private void validateDetailExists(Collection<Long> ids) {
        if(CollUtil.isEmpty(ids)){
            return ;
        }
        if(payrollDetailMapper.selectCount(ids) != ids.size()){
            throw exception(PAYROLL_DETAIL_ID_NOT_EXISTS);
        }
    }

    private void validatePayrollIdAndEmployeeIdUncreated(Long payrollId, Long employeeId, Collection<PayrollDetailFieldSaveBO> createBOs) {
        PayrollDetailDO detail = payrollDetailMapper.selectByPayrollIdAndEmployeeId(payrollId, employeeId);
        if(detail != null){
            createBOs.forEach(createBO -> createBO.setRecordId(detail.getId()));
            throw exception(PAYROLL_DETAIL_CREATED);
        }
    }

    private void validatePayrollIdAndEmployeeIdNotExists(Long payrollId, Long employeeId, String employeeNo) {
        // 关闭数据权限，避免因为没有数据权限，查询不到数据，进而导致唯一校验不正确
        DataPermissionUtils.executeIgnore(() -> {
            if(payrollDetailMapper.selectByPayrollIdAndEmployeeId(payrollId, employeeId) != null){
                throw exception(PAYROLL_DETAIL_EXISTS, employeeNo);
            }
        });
    }

    private void validateEmployeeNoDuplicate(List<PayrollDetailSaveBO> details) {
        if(CollectionUtils.convertSet(details, PayrollDetailSaveBO::getEmployeeNo).size() != details.size()){
            throw exception(PAYROLL_DETAIL_EMPLOYEE_NO_DUPLICATE);
        }
    }

    private void validateEmployeeIdExists(Collection<Long> employeeIds, Long payrollId) {
        if(CollUtil.isEmpty(employeeIds)){
            return ;
        }
        if(payrollDetailMapper.selectCountByPayrollIdAndEmployeeId(payrollId, employeeIds) != employeeIds.size()){
            throw exception(PAYROLL_DETAIL_EMPLOYEE_ID_NOT_EXISTS);
        }
    }

    private void validateApproveResultAndSendStatusByEmployeeIdAndPayrollId(Long payrollId, Collection<Long> employeeIds) {
        if(payrollDetailMapper.selectCountByPayrollIdAndEmployeeIdAndApproveResultAndConfirmStatus(payrollId, employeeIds,
                BpmProcessInstanceStatusEnum.RUNNING.getStatus(), null, false) > 0){
            throw exception(PAYROLL_DETAIL_UPDATE_APPROVE_RESULT_ERROR);
        }
        if(payrollDetailMapper.selectCountByPayrollIdAndEmployeeIdAndSendStatus(payrollId, employeeIds,
                PayrollSendStatusEnum.SENT.getValue()) > 0){
            throw exception(PAYROLL_DETAIL_UPDATE_SEND_STATUS_ERROR);
        }
    }

    private void validateApproveResultAndSendStatusAndConfirmStatus(Long id) {
        PayrollDetailDO detail = payrollDetailMapper.selectById(id);
        if(Objects.isNull(detail)){
            throw exception(PAYROLL_DETAIL_NOT_EXISTS);
        }
        if(BpmProcessInstanceStatusEnum.RUNNING.getStatus().equals(detail.getApproveResult())){
            throw exception(PAYROLL_DETAIL_UPDATE_APPROVE_RESULT_ERROR);
        }
        if(PayrollSendStatusEnum.SENT.getValue().equals(detail.getSendStatus())){
            throw exception(PAYROLL_DETAIL_UPDATE_SEND_STATUS_ERROR);
        }
    }

    private void validateIdAndPayrollIdConsistent(Collection<Long> ids, Long payrollId) {
        if(CollUtil.isEmpty(ids)){
            return ;
        }
        if(payrollDetailMapper.selectCountByIdAndPayrollId(ids, payrollId) != ids.size()){
            throw exception(PAYROLL_DETAIL_ID_NOT_EXISTS);
        }
    }

    private void validateApproveResultAndSendStatus(Collection<Long> ids) {
        if(CollUtil.isEmpty(ids)){
            return ;
        }
        if(payrollDetailMapper.selectCountByIdAndApproveResult(ids, BpmProcessInstanceStatusEnum.RUNNING.getStatus()) > 0){
            throw exception(PAYROLL_DETAIL_APPROVE_APPROVE_RESULT_ERROR);
        }
        if(payrollDetailMapper.selectCountByIdAndSendStatus(ids, PayrollSendStatusEnum.SENT.getValue()) > 0){
            throw exception(PAYROLL_DETAIL_UPDATE_SEND_STATUS_ERROR);
        }
    }

    private void validateApprovePassedAndNotSend(Long payrollId, Collection<Long> ids) {
        if(Objects.isNull(payrollId)){
            return ;
        }
        if(!Objects.equals(payrollDetailMapper.selectCountByPayrollIdAndIdAndApproveResult(ids, payrollId,
                BpmProcessInstanceStatusEnum.APPROVE.getStatus(), false), payrollDetailMapper.selectCountByIdAndPayrollId(ids, payrollId))){
            if(payrollDetailMapper.selectCountByPayrollIdAndIdAndApproveResult(ids, payrollId, null, true) > 0){
                throw exception(PAYROLL_DETAIL_NOT_APPROVE_ERROR);
            }
            throw exception(PAYROLL_DETAIL_APPROVE_RESULT_NOT_PASSED);
        }
        if(payrollDetailMapper.selectCountByIdAndPayrollIdAndSendStatus(ids, payrollId, PayrollSendStatusEnum.SENT.getValue()) > 0){
            throw exception(PAYROLL_DETAIL_UPDATE_SEND_STATUS_ERROR);
        }
    }

    private void validateAllowUpdate(Collection<Long> employeeIds, Long payrollId) {
        if(CollUtil.isEmpty(employeeIds)){
            return ;
        }
        if(payrollDetailMapper.selectCountByPayrollIdAndEmployeeIdAndApproveResultAndConfirmStatus(payrollId, employeeIds,
                BpmProcessInstanceStatusEnum.APPROVE.getStatus(), true, true) > 0){
            throw exception(PAYROLL_DETAIL_APPROVED);
        }
    }

    private void validateAllowDelete(Long id) {
        validateApproveResultAndSendStatusAndConfirmStatus(id);
        PayrollDetailDO detail = payrollDetailMapper.selectById(id);
        if(BpmProcessInstanceStatusEnum.APPROVE.getStatus().equals(detail.getApproveResult()) &&
                !PayrollSendStatusEnum.WITHDRAW.getValue().equals(detail.getSendStatus())){
            throw exception(PAYROLL_DETAIL_DELETE_APPROVED_ERROR);
        }
    }

    private void validateAllowWithdraw(Collection<Long> ids) {
        if(CollUtil.isEmpty(ids)){
            return ;
        }
        if(payrollDetailMapper.selectCountByIdAndSendStatus(ids, PayrollSendStatusEnum.SENT.getValue()) != ids.size()){
            throw exception(PAYROLL_DETAIL_WITHDRAW_SEND_STATUS_ERROR);
        }
        if(payrollDetailMapper.selectCountByIdAndConfirmStatus(ids, true) > 0){
            throw exception(PAYROLL_DETAIL_WITHDRAW_CONFIRM_STATUS_ERROR);
        }
    }

    private void validateEmployeeRetire(Collection<Long> ids) {
        List<PayrollDetailDO> detailList = payrollDetailMapper.selectBatchIds(ids);
        if(CollUtil.isEmpty(detailList)){
            throw exception(PAYROLL_DETAIL_ID_NOT_EXISTS);
        }
        List<EmployeeDO> employeeList = employeeService.getEmployeeList(CollectionUtils.convertList(detailList, PayrollDetailDO::getEmployeeId));
        if(CollUtil.isEmpty(employeeList) || employeeList.size() != detailList.size()){
            throw exception(PAYROLL_DETAIL_EMPLOYEE_NOT_EXISTS);
        }
        if(CollectionUtils.anyMatch(employeeList, employee -> !EmployeeStatusEnum.SEPARATED.getCode().equals(employee.getState()))){
            throw exception(PAYROLL_DETAIL_EMPLOYEE_NOT_RETIRE);
        }
    }

    private void validateArchiveDetailExists(Long payrollId) {
        if(payrollDetailMapper.selectCountByIdAndPayrollId(null, payrollId) == 0){
            throw exception(PAYROLL_DETAIL_ARCHIVE_EMPTY);
        }
    }

    private void validateAllConfirm(Long payrollId) {
        if(!Objects.equals(payrollDetailMapper.selectCountByIdAndPayrollId(null, payrollId),
                payrollDetailMapper.selectCountByPayrollIdAndConfirmStatus(payrollId, true))){
            throw exception(PAYROLL_DETAIL_NOT_ALL_CONFIRM);
        }
    }

    private void validatePayrollNotArchive(Long id) {
        PayrollDetailDO detail = payrollDetailMapper.selectById(id);
        if(Objects.isNull(detail)){
            throw exception(PAYROLL_NOT_EXISTS);
        }
        validatePayrollNotArchiveByPayrollId(detail.getPayrollId());
    }

    private void validatePayrollNotArchiveByPayrollId(Long payrollId) {
        payrollService.validateNotArchive(payrollId);
    }

    private void validateDetailNotArchive(Long id) {
        PayrollDetailDO detail = payrollDetailMapper.selectById(id);
        if(Objects.isNull(detail)){
            throw exception(PAYROLL_DETAIL_NOT_EXISTS);
        }
        if(Boolean.TRUE.equals(detail.getArchived())){
            throw exception(PAYROLL_DETAIL_ARCHIVED);
        }
    }

    private void validateDetailNotArchive(Collection<Long> ids) {
        List<PayrollDetailDO> details = payrollDetailMapper.selectBatchIds(ids);
        if(CollectionUtils.anyMatch(details, PayrollDetailDO::getArchived)){
            throw exception(PAYROLL_DETAIL_ARCHIVED);
        }
    }

    private void validateUpdated(Collection<Long> ids) {
        if(payrollDetailMapper.selectCountByIdAndUpdated(ids, false) > 0){
            throw exception(PAYROLL_DETAIL_NOT_UPDATED);
        }
    }

    @Override
    public PayrollDetailDO getDetail(Long id) {
        return payrollDetailMapper.selectById(id);
    }

    @Override
    public List<PayrollDetailDO> getDetailListByPayrollId(Long payrollId) {
        return payrollDetailMapper.selectListByPayrollId(payrollId);
    }

    @Override
    public List<PayrollDetailDO> getDetailListByPayrollIdAndEmployeeId(Long payrollId, Long employeeId) {
        return payrollDetailMapper.getDetailListByPayrollIdAndEmployeeId(payrollId, employeeId);
    }

    @Override
    public PageResult<PayrollDetailDO> getDetailPage(PayrollDetailPageReqVO pageReqVO) {
        return payrollDetailMapper.selectPage(pageReqVO);
    }

}